package org.activequant.math.algorithms;

import org.apache.log4j.Logger;

/**
 * Rolling linear regression with EMA averaging. Learns linear relationship
 * <pre>
 * y_k = alpha + phi * x_k + epsilon
 * </pre> 
 * where x_k is input measurements, y_k are output measurements, {alpha, phi} are linear model
 * parameters, and error epsilon is a normally distributed random "noise" with variance sigma.
 * <br/>
 * Holds the following associated variables:
 * <ul>
 * <li>x(EMAAccumulator)</li>
 * <li>f(EMAAccumulator)</li>
 * <li>b(EMAAccumulator)</li>
 * <li>i(EMAAccumulator)</li>
 * <li>k(EMAAccumulator)</li>
 * </ul>
 * <b>History:</b><br>
 *  - [18.02.2008] Created (Mike Kroutikov)<br>
 *
 *  @author Mike Kroutikov
 */
public class EMARegression {
	
	@SuppressWarnings("unused")
	private Logger log = Logger.getLogger(getClass());
	/**
	 * public static final double INFTY   = 1.e10;
	 */
	public static final double INFTY   = 1.e10;
	/**
	 * public static final double EPSILON = 1.e-10;
	 */
	public static final double EPSILON = 1.e-10;
	/**
	 * private final EMAAccumulator x = new EMAAccumulator();
	 */
	private final EMAAccumulator x = new EMAAccumulator(); 
	/**
	 * private final EMAAccumulator f = new EMAAccumulator();
	 */
	private final EMAAccumulator f = new EMAAccumulator(); 
	/**
	 * private final EMAAccumulator b = new EMAAccumulator(); 
	 */
	private final EMAAccumulator b = new EMAAccumulator(); 
	/**
	 * private final EMAAccumulator i = new EMAAccumulator();
	 */
	private final EMAAccumulator i = new EMAAccumulator();
	/**
	 * private final EMAAccumulator k = new EMAAccumulator();
	 */
	private final EMAAccumulator k = new EMAAccumulator();
	/**
	 * constructs an EMARegression using the given period(int) to set the period(int) and lambda(double)=1-(1/period) of its associated x(EMAAccumulator), f(EMAAccumulator),
	 * b(EMAAccumulator), i(EMAAccumulator) and k(EMAAccumulator)
	 * @param period
	 */
	public EMARegression(int period) {
		x.setPeriod(period);
		f.setPeriod(period);
		b.setPeriod(period);
		i.setPeriod(period);
		k.setPeriod(period);
	}
	/**
	 * accumulates the given yy(double) and xx(double) into the associated x(EMAAccumulator), f(EMAAccumulator),
	 * b(EMAAccumulator), i(EMAAccumulator) and k(EMAAccumulator)
	 * @param yy
	 * @param xx
	 */
	public void accumulate(double yy, double xx) {

		double xxPrev = x.getMeanValue();
		x.accumulate(xx);
		
		double ff = yy - xx;
		
		double ffPrev = f.getMeanValue();
		f.accumulate(ff);
		
		i.accumulate((ff - ffPrev) * (ff - f.getMeanValue()));
		k.accumulate((ff - ffPrev) * (xx - x.getMeanValue()));
		b.accumulate((xx - xxPrev) * (xx - x.getMeanValue()));
	}
	/**
	 * returns the period(int) of the associated x(EMAAccumulator)
	 * @return
	 */
	public int getPeriod() { 
		return x.getPeriod();
	}
	/**
	 * returns the numSamples(int) of the associated x(EMAAccumulator)
	 * @return
	 */
	public int getNumSamples() { 
		return x.getNumSamples();
	}
	/**
	 * if(!isReady()) return 1. - EPSILON - EPSILON;<br/>
	 * return 1. + k.getMeanValue() / b.getMeanValue();
	 * @return
	 */
	public double getPhi() {
		if(!isReady()) return 1. - EPSILON - EPSILON;
		return 1. + k.getMeanValue() / b.getMeanValue();
	}
	/**
	 * double phi = getPhi();<br/>
	 * if(phi >= 1.0 - EPSILON) return INFTY; // explosion<br/>
	 * return - 1. / Math.log(phi);
	 * @return
	 */
	public double getTau() {
		double phi = getPhi();
		if(phi >= 1.0 - EPSILON) return INFTY; // explosion
		return - 1. / Math.log(phi);
	}
	/**
	 * if(!isReady()) return 0.0;<br/>
	 * return i.getMeanValue() - k.getMeanValue() * k.getMeanValue() / b.getMeanValue();
	 * @return
	 */
	public double getSigma() {
		if(!isReady()) return 0.0;
		return i.getMeanValue() - k.getMeanValue() * k.getMeanValue() / b.getMeanValue();
	}
	/**
	 * returns x.getMeanValue() - b.getMeanValue() * f.getMeanValue() / k.getMeanValue();
	 * @return
	 */
	public double getMu() {
		return x.getMeanValue() - b.getMeanValue() * f.getMeanValue() / k.getMeanValue();
	}
	/**
	 * returns f.getMeanValue() - x.getMeanValue() * k.getMeanValue() / b.getMeanValue();
	 * @return
	 */
	public double getAlpha() {
		return f.getMeanValue() - x.getMeanValue() * k.getMeanValue() / b.getMeanValue();
	}
	/**
	 * double absk = Math.abs(k.getMeanValue());<br/>
	 * return b.getMeanValue() > EPSILON * absk;
	 * @return
	 */
	public boolean isReady() {
		double absk = Math.abs(k.getMeanValue());
		return b.getMeanValue() > EPSILON * absk;
	}

	public String toString() {
		return getClass().getSimpleName() + ": period=" + x.getPeriod() + ", numSamples=" + x.getNumSamples() + ", mu=" + getMu() 
		+ ", phi=" + getPhi() + ", sigma=" + getSigma();
	}
}

